package com.btb.adapter.contract;

import static java.util.stream.Collectors.toList;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.web3j.abi.EventEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.DynamicArray;
import org.web3j.abi.datatypes.Event;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Uint;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.Credentials;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.RemoteFunctionCall;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.response.BaseEventResponse;
import org.web3j.protocol.core.methods.response.EthLog;
import org.web3j.protocol.core.methods.response.Log;
import org.web3j.protocol.core.methods.response.TransactionReceipt;

import com.btb.adapter.vo.OffChainEventEventResponse;
import com.btb.adapter.vo.UpChainEventEventResponse;
import com.btb.core.contract.BaseContractFunction;

import io.reactivex.Flowable;
import io.reactivex.functions.Function;

public class ActHero extends BaseContractFunction {

	public static final String FUNC_UPCHAIN = "upChain";

	public static final String FUNC_OFFCHAIN = "offChain";

	public static final String FUNC_HERODETAIL = "heroDetail";

	public ActHero(String contractAddress, Web3j web3j, Credentials credentials) {
		super(contractAddress, web3j, credentials);
	}

	public static final Event OFFCHAINEVENT_EVENT = new Event("OffChainEvent",
			Arrays.<TypeReference<?>>asList(new TypeReference<Address>() {
			}, new TypeReference<Address>() {
			}, new TypeReference<DynamicArray<Uint256>>() {
			}, new TypeReference<Uint256>() {
			}, new TypeReference<Uint256>() {
			}));;

	public static final Event UPCHAINEVENT_EVENT = new Event("UpChainEvent",
			Arrays.<TypeReference<?>>asList(new TypeReference<Address>() {
			}, new TypeReference<Address>() {
			}, new TypeReference<Uint256>() {
			}, new TypeReference<Uint256>() {
			}));;

	public RemoteFunctionCall<TransactionReceipt> upChain(String _address, BigInteger _tokenId, BigInteger _heroArea,
			BigInteger _grade, BigInteger _season, BigInteger _powerValue, BigInteger _productionDate, String _name,
			String _cardTips, String _cardImage) {
		final org.web3j.abi.datatypes.Function function = new org.web3j.abi.datatypes.Function(FUNC_UPCHAIN,
				Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(_address),
						new org.web3j.abi.datatypes.generated.Uint256(_tokenId),
						new org.web3j.abi.datatypes.generated.Uint256(_heroArea),
						new org.web3j.abi.datatypes.generated.Uint256(_grade),
						new org.web3j.abi.datatypes.generated.Uint256(_season),
						new org.web3j.abi.datatypes.generated.Uint256(_powerValue),
						new org.web3j.abi.datatypes.generated.Uint256(_productionDate),
						new org.web3j.abi.datatypes.Utf8String(_name),
						new org.web3j.abi.datatypes.Utf8String(_cardTips),
						new org.web3j.abi.datatypes.Utf8String(_cardImage)),
				Collections.<TypeReference<?>>emptyList());
		return executeRemoteCallTransaction(function);
	}

	public RemoteFunctionCall<TransactionReceipt> offChain(String _address, List<BigInteger> _tokenIds,
			BigInteger _clientid) {
		final org.web3j.abi.datatypes.Function function = new org.web3j.abi.datatypes.Function(FUNC_OFFCHAIN,
				Arrays.<Type>asList(new org.web3j.abi.datatypes.Address(_address),
						new org.web3j.abi.datatypes.DynamicArray<org.web3j.abi.datatypes.generated.Uint256>(
								org.web3j.abi.datatypes.generated.Uint256.class,
								org.web3j.abi.Utils.typeMap(_tokenIds,
										org.web3j.abi.datatypes.generated.Uint256.class)),
						new org.web3j.abi.datatypes.generated.Uint256(_clientid)),
				Collections.<TypeReference<?>>emptyList());
		return executeRemoteCallTransaction(function);
	}

	public List<Type> heroDetail(BigInteger _tokenId) {
		final org.web3j.abi.datatypes.Function function = new org.web3j.abi.datatypes.Function(FUNC_HERODETAIL,
				Arrays.<Type>asList(new org.web3j.abi.datatypes.generated.Uint256(_tokenId)),
				Arrays.asList(new TypeReference<Uint>() {
				}, new TypeReference<Uint>() {
				}, new TypeReference<Uint>() {
				}, new TypeReference<Uint>() {
				}, new TypeReference<Uint>() {
				}, new TypeReference<Utf8String>() {
				}, new TypeReference<Utf8String>() {
				}, new TypeReference<Utf8String>() {
				}));
		return readOnlyFunction(function);
	}

	public List<UpChainEventEventResponse> getUpChainEventEvents(TransactionReceipt transactionReceipt) {
		List<EventValuesWithLog> valueList = extractEventParametersWithLog(UPCHAINEVENT_EVENT, transactionReceipt);
		ArrayList<UpChainEventEventResponse> responses = new ArrayList<UpChainEventEventResponse>(valueList.size());
		for (EventValuesWithLog eventValues : valueList) {
			UpChainEventEventResponse typedResponse = new UpChainEventEventResponse();
			typedResponse.log = eventValues.getLog();
			typedResponse._operateAddr = (String) eventValues.getNonIndexedValues().get(0).getValue();
			typedResponse._targetAddr = (String) eventValues.getNonIndexedValues().get(1).getValue();
			typedResponse._tokenId = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue();
			typedResponse._type = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue();
			responses.add(typedResponse);
		}
		return responses;
	}

	public List<OffChainEventEventResponse> getOffChainEventEvents(TransactionReceipt transactionReceipt) {
		List<EventValuesWithLog> valueList = extractEventParametersWithLog(OFFCHAINEVENT_EVENT, transactionReceipt);
		ArrayList<OffChainEventEventResponse> responses = new ArrayList<OffChainEventEventResponse>(
				valueList.size());
		for (EventValuesWithLog eventValues : valueList) {
			OffChainEventEventResponse typedResponse = new OffChainEventEventResponse();
			typedResponse.log = eventValues.getLog();
			typedResponse._operateAddr = (String) eventValues.getNonIndexedValues().get(0).getValue();
			typedResponse._targetAddr = (String) eventValues.getNonIndexedValues().get(1).getValue();
			typedResponse._tokenIds = (List<BigInteger>) eventValues.getNonIndexedValues().get(2).getValue();
			typedResponse._clientid = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue();
			typedResponse._type = (BigInteger) eventValues.getNonIndexedValues().get(4).getValue();
			responses.add(typedResponse);
		}
		return responses;
	}

	public Flowable<OffChainEventEventResponse> offChainEventEventFlowable(EthFilter filter) {
		return web3j.ethLogFlowable(filter).map(new Function<Log, OffChainEventEventResponse>() {
			@Override
			public OffChainEventEventResponse apply(Log log) {
				EventValuesWithLog eventValues = extractEventParametersWithLog(OFFCHAINEVENT_EVENT, log);
				OffChainEventEventResponse typedResponse = new OffChainEventEventResponse();
				typedResponse.log = log;
				typedResponse._operateAddr = (String) eventValues.getNonIndexedValues().get(0).getValue();
				typedResponse._targetAddr = (String) eventValues.getNonIndexedValues().get(1).getValue();
				typedResponse._tokenIds = (List<BigInteger>) eventValues.getNonIndexedValues().get(2).getValue();
				typedResponse._clientid = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue();
				typedResponse._type = (BigInteger) eventValues.getNonIndexedValues().get(4).getValue();
				return typedResponse;
			}
		});
	}

	public List<OffChainEventEventResponse> offChainEventEventFlowableEx(EthFilter filter) {
		try {
			EthLog send = web3j.ethGetLogs(filter).send();
			return send.getLogs().stream().map(l -> {
				Log log = (Log) l;
				EventValuesWithLog eventValues = extractEventParametersWithLog(OFFCHAINEVENT_EVENT, log);
				OffChainEventEventResponse typedResponse = new OffChainEventEventResponse();
				typedResponse.log = log;
				typedResponse._operateAddr = (String) eventValues.getNonIndexedValues().get(0).getValue();
				typedResponse._targetAddr = (String) eventValues.getNonIndexedValues().get(1).getValue();
				typedResponse._tokenIds = (List<BigInteger>) eventValues.getNonIndexedValues().get(2).getValue();
				typedResponse._clientid = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue();
				typedResponse._type = (BigInteger) eventValues.getNonIndexedValues().get(4).getValue();
				return typedResponse;
			}).collect(toList());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return Collections.emptyList();
	}

	public Flowable<OffChainEventEventResponse> offChainEventEventFlowable(DefaultBlockParameter startBlock,
			DefaultBlockParameter endBlock) {
		EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress());
		filter.addSingleTopic(EventEncoder.encode(OFFCHAINEVENT_EVENT));
		return offChainEventEventFlowable(filter);
	}

	public List<OffChainEventEventResponse> offChainEventEventFlowableEx(DefaultBlockParameter startBlock,
			DefaultBlockParameter endBlock) {
		EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress());
		filter.addSingleTopic(EventEncoder.encode(OFFCHAINEVENT_EVENT));
		return offChainEventEventFlowableEx(filter);
	}

	public Flowable<UpChainEventEventResponse> upChainEventEventFlowable(EthFilter filter) {
		return web3j.ethLogFlowable(filter).map(new Function<Log, UpChainEventEventResponse>() {
			@Override
			public UpChainEventEventResponse apply(Log log) {
				EventValuesWithLog eventValues = extractEventParametersWithLog(UPCHAINEVENT_EVENT, log);
				UpChainEventEventResponse typedResponse = new UpChainEventEventResponse();
				typedResponse.log = log;
				typedResponse._operateAddr = (String) eventValues.getNonIndexedValues().get(0).getValue();
				typedResponse._targetAddr = (String) eventValues.getNonIndexedValues().get(1).getValue();
				typedResponse._tokenId = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue();
				typedResponse._type = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue();
				return typedResponse;
			}
		});
	}

	public List<UpChainEventEventResponse> upChainEventEventFlowableEx(EthFilter filter) {
		try {
			EthLog send = web3j.ethGetLogs(filter).send();
			return send.getLogs().stream().map(l -> {
				Log log = (Log) l;
				EventValuesWithLog eventValues = extractEventParametersWithLog(UPCHAINEVENT_EVENT, log);
				UpChainEventEventResponse typedResponse = new UpChainEventEventResponse();
				typedResponse.log = log;
				typedResponse._operateAddr = (String) eventValues.getNonIndexedValues().get(0).getValue();
				typedResponse._targetAddr = (String) eventValues.getNonIndexedValues().get(1).getValue();
				typedResponse._tokenId = (BigInteger) eventValues.getNonIndexedValues().get(2).getValue();
				typedResponse._type = (BigInteger) eventValues.getNonIndexedValues().get(3).getValue();
				return typedResponse;
			}).collect(toList());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return Collections.emptyList();
	}

	public Flowable<UpChainEventEventResponse> upChainEventEventFlowable(DefaultBlockParameter startBlock,
			DefaultBlockParameter endBlock) {
		EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress());
		filter.addSingleTopic(EventEncoder.encode(UPCHAINEVENT_EVENT));
		return upChainEventEventFlowable(filter);
	}

	public List<UpChainEventEventResponse> upChainEventEventFlowableEx(DefaultBlockParameter startBlock,
			DefaultBlockParameter endBlock) {
		EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress());
		filter.addSingleTopic(EventEncoder.encode(UPCHAINEVENT_EVENT));
		return upChainEventEventFlowableEx(filter);
	}

	private List<String> getContractAddress() {
		return Arrays.asList(contractAddress);
	}

	public static class ApprovalEventResponse extends BaseEventResponse {
		public String owner;

		public String approved;

		public BigInteger tokenId;
	}

	public static class ApprovalForAllEventResponse extends BaseEventResponse {
		public String owner;

		public String operator;

		public Boolean approved;
	}

	public static class PausedEventResponse extends BaseEventResponse {
		public String account;
	}

	public static class RoleAdminChangedEventResponse extends BaseEventResponse {
		public byte[] role;

		public byte[] previousAdminRole;

		public byte[] newAdminRole;
	}

	public static class RoleGrantedEventResponse extends BaseEventResponse {
		public byte[] role;

		public String account;

		public String sender;
	}

	public static class RoleRevokedEventResponse extends BaseEventResponse {
		public byte[] role;

		public String account;

		public String sender;
	}

	public static class TransferEventResponse extends BaseEventResponse {
		public String from;

		public String to;

		public BigInteger tokenId;
	}

	public static class UnpausedEventResponse extends BaseEventResponse {
		public String account;
	}

}
